Completed
Push — master ( b30a65...75e50f )
by Esaú
01:46
created

hierarchy-helper.js ➔ ???   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
cc 1
c 9
b 0
f 0
nc 1
dl 0
loc 1
rs 10
nop 0
1
// spec/helpers/hierarchy-helper.js
2
"use strict";
3
4
// :: DEPENDENCIES
5
6
// load native dependencies
7
const path = require("path");
8
const root = path.dirname(path.dirname(__dirname));
9
10
// global variables
11
const noStr   = [{}, true, false, 42, 3.1416, -42, -3.1416, () => null];
12
const noNmb   = [{}, true, false, '', "qwerty", () => null];
13
const name    = "qwerty";
14
const message = "asdf";
15
16
if (typeof Symbol === "function") {
17
    noStr.push(Symbol("symbol"));
18
    noNmb.push(Symbol("symbol"));
19
}
20
21
module.exports = (hierarchy) => {
22
    // check parameters
23
    if (!Array.isArray(hierarchy)) {
24
        throw new Error("hierarchy must be an array");
25
    }
26
27
    // load dependencies
28
    let deps      = hierarchy.map(value => require(path.join(root, value + ".js")));
29
    let klassName = hierarchy.pop();
30
    let Klass     = deps.pop();
31
    const first   = (hierarchy.length === 0);
32
    const third   = (hierarchy.length >= 3);
33
34
    // :: TESTING
35
36
    // test the last class in the hierarchy tree
37
    describe(klassName, () => {
38
39
        // :: INHERITED PROTOTYPE
40
41
        // all inherit from Object
42
        it("should inherit from 'Object'", () => {
43
            expect(new Klass()).toEqual(jasmine.any(Object));
44
        });
45
46
        // check the hierarchy tree
47
        for (let i = 0; i < hierarchy.length; i += 1) {
48
            it("should inherit from '" + hierarchy[i] + "'", () => {
49
                expect(new Klass()).toEqual(jasmine.any(deps[i]));
50
            });
51
        }
52
53
        // check inherited properties
54
        if (!first) {
55
            it("should have a prototype property named 'name'", () => {
56
                expect(Klass.prototype).toHaveString("name");
57
            });
58
59
            it("should have a prototype property named 'message'", () => {
60
                expect(Klass.prototype).toHaveString("message");
61
            });
62
63
            it("should have a prototype property named 'code'", () => {
64
                expect(Klass.prototype).toHaveMember("code");
65
            });
66
        }
67
68
        // check Object methods
69
        it("should have a prototype method named 'toString()'", () => {
70
            expect(Klass.prototype).toHaveMethod("toString");
71
        });
72
73
        // check inherited methods
74
        if (!first) {
75
            it("should have a prototype method named 'native()'", () => {
76
                expect(Klass.prototype).toHaveMethod("native");
77
            });
78
        }
79
80
        // :: EXTENDED PROTOTYPE
81
82
        // check extended properties
83
        if (first) {
84
            it("should have a prototype property named 'name'", () => {
85
                expect(Klass.prototype).toHaveString("name");
86
            });
87
88
            it("should have a prototype property named 'message'", () => {
89
                expect(Klass.prototype).toHaveString("message");
90
            });
91
92
            it("should have a prototype property named 'code'", () => {
93
                expect(Klass.prototype).toHaveMember("code");
94
            });
95
96
            it("should have a prototype method named 'native()'", () => {
97
                expect(Klass.prototype).toHaveMethod("native");
98
            });
99
        }
100
101
        // :: PROTOTYPE VALUES
102
103
        it("should have the 'class' name in the prototype property named 'name'", () => {
104
            expect(Klass.prototype.name).toEqual(klassName);
105
        });
106
107
        it("should have a dummy default value as message", () => {
108
            expect(Klass.prototype.message).toEqual("thrown");
109
        });
110
111
        it("should have a null default value as code", () => {
112
            expect(Klass.prototype.code).toBeNull();
113
        });
114
115
        // :: CONSTRUCTOR
116
117
        it("should instantiate without parameters", () => {
118
            instanceNoParameters(Klass, testNoErrors, third);
119
            testNoErrors(() => new Klass());
120
        });
121
122
        it("should instantiate with parameters", () => {
123
            instanceParameters(Klass, testNoErrors, testNoErrors, testNoErrors, third);
124
        });
125
126
        // use the tests according to the hierarchy level
127
        if (third) {
128
            testThird(Klass);
129
        } else {
130
            test(Klass);
131
        }
132
133
    });
134
135
};
136
137
// Tests that a function doesn't throw any Error
138
function testNoErrors(fn) {
139
    expect(fn).not.toThrowError("parameter 'name' must be a 'string'");
140
    expect(fn).not.toThrowError("parameter 'message' must be a 'string'");
141
    expect(fn).not.toThrowError("parameter 'code' must be a 'number'");
142
}
143
144
// Tests instantiation of a class without parameters (undefined, null or none).
145
function instanceNoParameters(Klass, fn, third) {
146
    let arg1, arg2, arg3, test;
147
    test = (() => new Klass(arg1, arg2, arg3));
148
    for (let i = 0; i < 2; i += 1) {
149
        arg1 = (i % 2 === 0 ? undefined : null);
150
        for (let j = 0; j < 2; j += 1) {
151
            arg2 = (j % 2 === 0 ? undefined : null);
152
            if (third) {
153
                fn(test);
154
            } else {
155
                for (let e = 0; e < 2; e += 1) {
156
                    arg3 = (e % 2 === 0 ? undefined : null);
157
                    fn(test);
158
                }
159
            }
160
        }
161
    }
162
}
163
164
// Tests instantiation of a class with parameters.
165
function instanceParameters(Klass, fn1, fn2, fn3, third) {
166
    let arg1, arg2, arg3, test3, args1, args2, args3;
167
    const test1 = (() => new Klass(arg1));
168
    const test2 = (() => new Klass(arg1, arg2));
169
    if (third) {
170
        test3 = (() => null);
171
        args1 = [undefined, null, Klass.prototype.message];
172
        args2 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
173
        args3 = [];
174
    } else {
175
        test3 = (() => new Klass(arg1, arg2, arg3));
176
        args1 = [undefined, null, Klass.prototype.name];
177
        args2 = [undefined, null, Klass.prototype.message];
178
        args3 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
179
    }
180
    for (let i = 0; i < args1.length; i += 1) {
181
        arg1 = args1[i];
182
        fn1(test1);
183
        for (let j = 0; j < args2.length; j += 1) {
184
            arg2 = args2[j];
185
            fn2(test2);
186
            for (let e = 0; !third && e < args3.length; e += 1) {
187
                arg3 = args3[e];
188
                fn3(test3);
189
            }
190
        }
191
    }
192
}
193
194
// Loops for each parameter of the Klass constructor.
195
// If the iteration is even, the parameter is defined.
196
// If the iteration is odd, the parameter is null.
197
function instanceDefinedOrNull(Klass, name, message, code, fn1, fn2, fn3, third) {
198
    for (let i = 0; i < 2; i += 1) {
199
        const even1   = (i % 2 === 0);
200
        const arg1    = (even1 ? (third ? message : name) : null);
201
        const source1 = new Klass(arg1);
202
        fn1(source1, even1);
203
        for (let j = 0; j < 2; j += 1) {
204
            const even2   = (j % 2 === 0);
205
            const arg2    = (even2 ? (third ? code : message) : null);
206
            const source2 = new Klass(arg1, arg2);
207
            fn2(source2, even1, even2);
208
            for (let e = 0; !third && e < 2; e += 1) {
209
                const even3   = (e % 2 === 0);
210
                const arg3    = (even3 ? code : null);
211
                const source3 = new Klass(arg1, arg2, arg3);
212
                fn3(source3, even1, even2, even3);
213
            }
214
        }
215
    }
216
}
217
218
// Loops for each parameter of the Klass constructor using wrong types to test Error throwing.
219
function instanceThrowErrors(Klass, fn1, fn2, fn3, third) {
220
    let arg1, arg2, arg3, test33, test32, test31, test21, test22, test11, len2, len3;
221
    const len1 = noStr.length;
222
    len2       = (third ? noNmb.length : len1);
223
    len3       = (third ? 0            : noNmb.length);
224
    test33     = (() => third ? null : new Klass(arg1, arg2, arg3));
225
    test32     = (() => third ? null : new Klass(null, arg2, arg3));
226
    test31     = (() => third ? null : new Klass(null, null, arg3));
227
    test22     = (() => new Klass(arg1, arg2));
228
    test21     = (() => new Klass(null, arg2));
229
    test11     = (() => new Klass(arg1));
230
    for (let i = 0; i < len1; i += 1) {
231
        arg1 = noStr[i];
232
        fn1(test11);
233
        for (let j = 0; j < len2; j += 1) {
234
            arg2 = (third ? noNmb[j] : noStr[j]);
235
            fn2(test21, test22);
236
            for (let e = 0; !third && e < len3; e += 1) {
237
                arg3 = noNmb[e];
238
                fn3(test31, test32, test33);
239
            }
240
        }
241
    }
242
}
243
244
// Tests classes that are a third level subclass, meaning that they require 2 arguments (message and code).
245
function testThird(Klass) {
246
247
    // :: CONSTRUCTOR
248
249
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
250
        instanceThrowErrors(Klass, (test11) => {
251
            expect(test11).toThrowError("parameter 'message' must be a 'string'");
252
        }, (test21, test22) => {
253
            expect(test21).toThrowError("parameter 'code' must be a 'number'");
254
            expect(test22).toThrowError("parameter 'message' must be a 'string'");
255
        }, null, true);
256
    });
257
258
    // :: MEMBER PROPERTIES
259
260
    const message = "asdf";
261
    const code    = Math.round(Math.random() * 0xFFFFFFFF);
262
263
    it("should have all correct properties once instantiated", () => {
264
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
265
            if (even) {
266
                expect(instance.name).toEqual(Klass.prototype.name);
267
                expect(instance.message).toEqual(message);
268
            } else {
269
                expect(instance.name).toEqual(Klass.prototype.name);
270
                expect(instance.message).toEqual(Klass.prototype.message);
271
            }
272
            expect(instance.code).toBeNull();
273
        }, (instance, even1, even2) => {
274
            expect(instance.name).toEqual(Klass.prototype.name);
275
            expect(instance.message).toEqual(even1 ? message : Klass.prototype.message);
276
            expect(instance.code).toEqual(even2 ? code : null);
277
        }, null, true);
278
    });
279
280
    // :: MEMBER METHODS
281
282
    it("#toString()", () => {
283
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
284
            let exp;
285
            if (even) {
286
                exp = Klass.prototype.name + ": " + message + '.';
287
            } else {
288
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
289
            }
290
            expect(instance.toString()).toEqual(exp);
291
        }, (instance, even1, even2) => {
292
            let exp;
293
            exp = Klass.prototype.name;
294
            exp += (even2 ? " (0x" + code.toString(16) + "):" : ':' ) + ' ';
295
            exp += (even1 ? message : Klass.prototype.message) + '.';
296
            expect(instance.toString()).toEqual(exp);
297
        }, null, true);
298
    });
299
300
    it("#native()", () => {
301
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
302
            const exp = (even ? message : Klass.prototype.message);
303
            expect(instance.native()).toEqual(new Error(exp));
304
        }, (instance, even1) => {
305
            const exp = (even1 ? message : Klass.prototype.message);
306
            expect(instance.native()).toEqual(new Error(exp));
307
        }, null, true);
308
    });
309
310
}
311
312
// Tests classes that require 3 arguments (name, message and code).
313
function test(Klass) {
314
315
    // :: CONSTRUCTOR
316
317
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
318
        instanceThrowErrors(Klass, (test11) => {
319
            expect(test11).toThrowError("parameter 'name' must be a 'string'");
320
        }, (test21, test22) => {
321
            expect(test22).toThrowError("parameter 'name' must be a 'string'");
322
            expect(test21).toThrowError("parameter 'message' must be a 'string'");
323
        }, (test31, test32, test33) => {
324
            expect(test33).toThrowError("parameter 'name' must be a 'string'");
325
            expect(test32).toThrowError("parameter 'message' must be a 'string'");
326
            expect(test31).toThrowError("parameter 'code' must be a 'number'");
327
        }, false);
328
    });
329
330
    // :: MEMBER PROPERTIES
331
332
    const code = Math.round(Math.random() * 0xFFFFFFFF);
333
334
    it("should have all correct properties once instantiated", () => {
335
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
336
            if (even) {
337
                expect(instance.name).toEqual(name);
338
                expect(instance.message).toEqual(Klass.prototype.message);
339
            } else {
340
                expect(instance.name).toEqual(Klass.prototype.name);
341
                expect(instance.message).toEqual(Klass.prototype.message);
342
            }
343
            expect(instance.code).toBeNull();
344
        }, (instance, even1, even2) => {
345
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
346
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
347
            expect(instance.code).toBeNull();
348
        }, (instance, even1, even2, even3) => {
349
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
350
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
351
            expect(instance.code).toEqual(even3 ? code : null);
352
        }, false);
353
    });
354
355
    // :: MEMBER METHODS
356
357
    it("#toString()", () => {
358
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
359
            let exp;
360
            if (even) {
361
                exp = name + ": " + Klass.prototype.message + '.';
362
            } else {
363
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
364
            }
365
            expect(instance.toString()).toEqual(exp);
366
        }, (instance, even1, even2) => {
367
            let exp;
368
            exp = (even1 ? name : Klass.prototype.name) + ':';
369
            exp += ' ' + (even2 ? message : Klass.prototype.message) + '.';
370
            expect(instance.toString()).toEqual(exp);
371
        }, (instance, even1, even2, even3) => {
372
            let exp;
373
            exp = (even1 ? name : Klass.prototype.name);
374
            exp += (even3 ? " (0x" + code.toString(16) + "):" : ':') + ' ';
375
            exp += (even2 ? message : Klass.prototype.message) + '.';
376
            expect(instance.toString()).toEqual(exp);
377
        }, false);
378
    });
379
380
    it("#native()", () => {
381
        instanceDefinedOrNull(Klass, name, message, code, (instance) => {
382
            const exp = Klass.prototype.message;
383
            expect(instance.native()).toEqual(new Error(exp));
384
        }, (instance, even1, even2) => {
385
            const exp = (even2 ? message : Klass.prototype.message);
386
            expect(instance.native()).toEqual(new Error(exp));
387
        }, (instance, even1, even2) => {
388
            const exp = (even2 ? message : Klass.prototype.message);
389
            expect(instance.native()).toEqual(new Error(exp));
390
        }, false);
391
    });
392
393
}